use procfs::process::Process;
use std::sync::Mutex;

use actix_web::{web, HttpResponse, Result};
use prometheus_client::encoding::text::encode;
use prometheus_client::encoding::{EncodeLabelSet, EncodeLabelValue};
use prometheus_client::metrics::counter::Counter;
use prometheus_client::metrics::family::Family;
use prometheus_client::registry::Registry;

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct CommLabels {
    pub comm_name: String,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
pub enum Method {
    Get,
    Post,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct MethodLabels {
    pub method: Method,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
pub enum CpuTime {
    User,
    System,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct CpuLabels {
    pub cpu_type: CpuTime,
    pub comm_name: String,
    pub pid: i32,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
pub enum IOType {
    Read,
    Write,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
pub enum DevType {
    Disk,
    Net,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct IOLabels {
    pub comm_name: String,
    pub pid: i32,
    pub io_type: IOType,
    pub dev: String,
    pub dev_type: DevType,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
pub enum CtxType {
    Voluntary,
    Nonvoluntary,
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct CtxLabels {
    pub comm_name: String,
    pub pid: i32,
    pub ctx_type: CtxType,
}

pub struct AppState {
    pub registry: Registry,
}

fn own_state_metrics(metrics: web::Data<OwnMetrics>) {
    let me = Process::myself().unwrap();
    let me_stat = me.stat().unwrap();
    let me_state = me.status().unwrap();

    metrics.update_cpu(me_stat.comm, me_stat.pid, CpuTime::System, me_stat.stime);

    let me_stat = me.stat().unwrap();
    metrics.update_cpu(me_stat.comm, me_stat.pid, CpuTime::User, me_stat.utime);
    let me_stat = me.stat().unwrap();
    metrics.update_ctx(
        me_stat.comm,
        me_stat.pid,
        CtxType::Voluntary,
        me_state.voluntary_ctxt_switches.unwrap_or_default(),
    );
    let me_stat = me.stat().unwrap();
    metrics.update_ctx(
        me_stat.comm,
        me_stat.pid,
        CtxType::Nonvoluntary,
        me_state.nonvoluntary_ctxt_switches.unwrap_or_default(),
    );
}

pub async fn metrics_handler(
    state: web::Data<Mutex<AppState>>,
    metrics: web::Data<OwnMetrics>,
) -> Result<HttpResponse> {
    own_state_metrics(metrics);
    let state = state.lock().unwrap();
    let mut body = String::new();
    encode(&mut body, &state.registry).unwrap();
    Ok(HttpResponse::Ok()
        .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8")
        .body(body))
}

pub struct OwnMetrics {
    pub requests: Family<MethodLabels, Counter>,
    pub cpu_time: Family<CpuLabels, Counter>,
    pub ctx_switch: Family<CtxLabels, Counter>,
    pub io_bytes: Family<IOLabels, Counter>,
}

impl OwnMetrics {
    pub fn inc_requests(&self, method: Method) {
        self.requests.get_or_create(&MethodLabels { method }).inc();
    }
    pub fn update_cpu(&self, comm_name: String, pid: i32, cpu_type: CpuTime, now_value: u64) {
        //let cpu_type_clone = cpu_t.clone();
        let cpu = self
            .cpu_time
            .get_or_create(&CpuLabels {
                comm_name,
                pid,
                cpu_type,
            })
            .clone();
        let v = cpu.get();
        let new_value = now_value - v;
        if new_value > 0 {
            cpu.inc_by(new_value);
        }
    }
    pub fn update_io(
        &self,
        comm_name: String,
        pid: i32,
        io_type: IOType,
        dev: String,
        dev_type: DevType,
    ) {
        self.io_bytes
            .get_or_create(&IOLabels {
                comm_name,
                pid,
                io_type,
                dev,
                dev_type,
            })
            .inc();
    }
    pub fn update_ctx(&self, comm_name: String, pid: i32, ctx_type: CtxType, now_value: u64) {
        let ctx = self
            .ctx_switch
            .get_or_create(&CtxLabels {
                comm_name,
                pid,
                ctx_type,
            })
            .clone();
        let v = ctx.get();

        let new_value = now_value - v;
        if new_value > 0 {
            ctx.inc_by(new_value);
        }
    }
}
